package com.box.app.arithmetic

class ArithmeticError private constructor(message: String) : Error(message) {
    private constructor(message: String, expression: String, index: Int, end: Int? = null) : this(
        message(
            message,
            expression,
            index,
            end,
        )
    )

    private constructor(message: String, factors: List<Factor>, index: Int, end: Int? = null) : this(
        message,
        factors.joinToDescribe(),
        index,
        end,
    )

    private constructor(message: String, factors: List<Factor>, index: Int, current: Describable) : this(
        message,
        factors,
        index + current.toTightRange().first,
        index + current.toTightRange().last,
    )

    internal constructor(e: Throwable, factors: List<Factor>, index: Int, current: Describable) : this(
        e.message ?: "",
        factors,
        index,
        current,
    )

    internal constructor(e: Throwable, factors: List<Factor>, index: Int) : this(message(e, factors, index))

    override val message: String = "${super.message}"

    internal companion object {
        private const val INDICATOR = '^'
        private const val INDENT = "  "

        internal fun curve(depth: Int, expression: String, index: Int, end: Int? = null): ArithmeticError {
            val buffer = StringBuilder()
            when {
                depth > 0 -> buffer.append("Can't find '(' to math ')'.")
                depth < 0 -> buffer.append("Can't find ')' to math '('.")
                else -> buffer.append("Invalid expression, the number of '(' is not equal to the number of ')'.")
            }
            return ArithmeticError(buffer.toString(), expression, index, end)
        }

        private fun curve(depth: Int, factors: List<Factor>, index: Int, end: Int? = null): ArithmeticError {
            return curve(depth, factors.joinToDescribe(), index, end)
        }

        private fun curve(depth: Int, factors: List<Factor>, index: Int, current: Describable): ArithmeticError {
            return curve(
                depth,
                factors,
                index + current.toTightRange().first,
                index + current.toTightRange().last
            )
        }

        internal fun curve(factors: List<Factor>, index: Int, current: Curve): ArithmeticError {
            val depth = when (current) {
                Curve.left -> 1
                Curve.right -> -1
            }
            return curve(depth, factors, index, current)
        }

        internal fun curveLeft(factors: List<Factor>, index: Int, current: Describable): ArithmeticError {
            return curve(1, factors, index, current)
        }

        internal fun curveRight(factors: List<Factor>, index: Int, current: Describable): ArithmeticError {
            return curve(-1, factors, index, current)
        }

        internal fun operator(factors: List<Factor>, index: Int, current: Operator): ArithmeticError {
            return ArithmeticError("'$current' is not a prefix operator.", factors, index, current)
        }

        internal fun function(factors: List<Factor>, index: Int, current: Math): ArithmeticError {
            return ArithmeticError("Found this candidate, but the arguments don't match.", factors, index, current)
        }

        internal fun character(expression: String, index: Int, end: Int? = null): ArithmeticError {
            val character = expression.substring(index, end ?: expression.length)
            return ArithmeticError("Expected an identifier, but got '$character'.", expression, index, end)
        }

        internal fun variable(symbol: String?): ArithmeticError {
            return ArithmeticError("Variable '$symbol' must be assigned before it can be used.")
        }

        private fun message(e: Throwable, factors: List<Factor>, index: Int): String {
            val lines = (e.message ?: "").split("\n")
            return if (lines.size == 3 && lines[2].contains(INDICATOR)) {
                val message = lines[0]
                val indicator = lines[2].substring(INDENT.length)
                val expression = factors.joinToDescribe()
                val start = index + indicator.indexOf(INDICATOR)
                val end = index + indicator.lastIndexOf(INDICATOR)
                message(message, expression, start, end + 1)
            } else {
                message(e.message ?: "", factors.joinToDescribe(), index)
            }
        }

        private fun message(message: String, expression: String, index: Int, end: Int? = null): String {
            val buffer = StringBuilder(message)
            buffer.append('\n')
            buffer.append(indicator(expression, index, end))
            return buffer.toString()
        }

        private fun indicator(expression: String, index: Int, end: Int? = null): String {
            val buffer = StringBuilder("$INDENT$expression")
            buffer.append('\n')
            buffer.append(INDENT)
            for (i in expression.indices) {
                buffer.append(if (if (end == null) index == i else i in index until end) INDICATOR else ' ')
            }
            return buffer.toString()
        }
    }
}